/* Last modified Time-stamp: <Ye Wenbin 2007-06-06 23:34:53>
 * @(#)hexl+.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iconv.h>
#include <assert.h>

#define DEFAULT_GROUPING	0x01
#define DEFAULT_BASE		16
#define LENGTH              16

#undef TRUE
#undef FALSE
#define TRUE  (1)
#define FALSE (0)

int base = DEFAULT_BASE, un_flag = FALSE, iso_flag = FALSE, endian = 1;
int group_by = DEFAULT_GROUPING;
char *progname;
char *fromcode = NULL;
char *tocode = NULL;

void usage();
void hexl_with_coding (FILE *fp);
void hexl(FILE *fp);
void hexl_line(char *s);
void noctrl(char *s);

int
main (argc, argv)
int argc;
char *argv[];
{
    FILE *fp;

    progname = *argv++; --argc;

    /*
    ** -hex		hex dump
    ** -oct		Octal dump
    ** -group-by-8-bits
    ** -group-by-16-bits
    ** -group-by-32-bits
    ** -group-by-64-bits
    ** -iso		iso character set.
    ** -big-endian	Big Endian
    ** -little-endian	Little Endian
    ** -un || -de	from hexl format to binary.
    ** -from      encoding of input
    ** -to        encoding of output
    ** --		End switch list.
    ** <filename>	dump filename
    ** -		(as filename == stdin)
    */

    while (*argv && *argv[0] == '-' && (*argv)[1])
    {
        /* A switch! */
        if (!strcmp (*argv, "--"))
        {
            --argc; argv++;
            break;
        }
        else if (!strcmp (*argv, "-un") || !strcmp (*argv, "-de"))
        {
            un_flag = TRUE;
            --argc; argv++;
        }
        else if (!strcmp (*argv, "-hex"))
        {
            base = 16;
            --argc; argv++;
        }
        else if (!strcmp (*argv, "-iso"))
        {
            iso_flag = TRUE;
            --argc; argv++;
        }
        else if (!strcmp (*argv, "-oct"))
        {
            base = 8;
            --argc; argv++;
        }
        else if (!strcmp (*argv, "-big-endian"))
        {
            endian = 1;
            --argc; argv++;
        }
        else if (!strcmp (*argv, "-little-endian"))
        {
            endian = 0;
            --argc; argv++;
        }
        else if (!strcmp (*argv, "-group-by-8-bits"))
        {
            group_by = 0x00;
            --argc; argv++;
        }
        else if (!strcmp (*argv, "-group-by-16-bits"))
        {
            group_by = 0x01;
            --argc; argv++;
        }
        else if (!strcmp (*argv, "-group-by-32-bits"))
        {
            group_by = 0x03;
            --argc; argv++;
        }
        else if (!strcmp (*argv, "-group-by-64-bits"))
        {
            group_by = 0x07;
            endian = 0;
            --argc; argv++;
        }
        else if (!strcmp (*argv, "-from"))
        {
            argv++; argc--;
            if ( *argv && *argv[0] != '-' ) {
                fromcode = *argv;
                argv++; argc--;
            }
            else {
                fprintf (stderr, "%s: no from encoding.\n", progname);
                usage();
            }
        }
        else if (!strcmp (*argv, "-to"))
        {
            argv++; argc--;
            if ( *argv && *argv[0] != '-' ) {
                tocode = *argv;
                argv++; argc--;
            }
            else {
                fprintf (stderr, "%s: no to encoding.\n", progname);
                usage();
            }
        }
        else
        {
            fprintf (stderr, "%s: invalid switch: \"%s\".\n", progname,
                     *argv);
            usage ();
        }
    }

    do {
        if (*argv == NULL)
            fp = stdin;
        else
        {
            char *filename = *argv++;

            if (!strcmp (filename, "-"))
                fp = stdin;
            else if ((fp = fopen (filename, "r")) == NULL)
            {
                perror (filename);
                continue;
            }
        }
        if (un_flag)
        {
            char buf[18];
            for (;;)
            {
                register int i, c = 0, d;

#define hexchar(x) (isdigit (x) ? x - '0' : x - 'a' + 10)

                fread (buf, 1, 10, fp); /* skip 10 bytes */

                for (i=0; i < LENGTH; ++i)
                {
                    if ((c = getc (fp)) == ' ' || c == EOF)
                        break;

                    d = getc (fp);
                    c = hexchar (c) * 0x10 + hexchar (d);
                    putchar (c);

                    if ((i&group_by) == group_by)
                        getc (fp); /* skip a blank */
                }

                if (c == ' ')   /* ? Why test c ? */
                {
                    while ((c = getc (fp)) != '\n' && c != EOF)
                        ;
                    if (c == EOF)
                        break;
                }
                else
                {
                    if (i < LENGTH)
                        break;
                    while ((c = getc (fp)) != '\n' && c != EOF)
                        ;
                    if (c == EOF)
                        break;
                }
            }
        }
        else
        {
            if ( fromcode != NULL && tocode != NULL ) {
                hexl_with_coding(fp);
            }
            else {
                hexl(fp);
            }
        }
        if (fp != stdin)
            fclose (fp);
    } while (*argv != NULL);
}

void hexl_with_coding (FILE *fp) 
{
    register long address;
    char out[LENGTH*2];
    char buf[LENGTH*2];
    char *inbuf = buf, *outbuf = out;
    char **pin, **pout;
    iconv_t cd;
    int leftbyte = 0;
    int size;
    int outlen = LENGTH*2, inlen;

    address = 0;
    if ( fromcode == NULL ) {
        fromcode = "char";
    }
    if ( tocode == NULL ) {
        tocode = "char";
    }
    cd = iconv_open(tocode, fromcode);
    if ( cd==(iconv_t)(-1) ) {
        fprintf(stderr, "Can't convert from %s to %s", fromcode, tocode);
        exit(EXIT_FAILURE);
    }
    memset(inbuf, 0, LENGTH*2);
    memset(outbuf, 0, LENGTH*2);
    while ( size=fread(buf+leftbyte, 1, LENGTH, fp) ) {
        buf[leftbyte+size] = '\0';
        inlen = LENGTH + leftbyte;
        pin = &inbuf;
        pout = &outbuf;
        iconv(cd, pin, &inlen, pout, &outlen);
        printf("%08lx: ", address);
        hexl_line(buf+leftbyte);
        noctrl(out);
        address += LENGTH;
        printf(" %s\n", out);
        if ( inlen != 0 ) {
            memcpy(buf, buf+LENGTH+leftbyte-inlen, inlen);
        }
        leftbyte = inlen;
        inbuf = buf;
        outbuf = out;
        outlen = LENGTH*2;
        memset(outbuf, 0, outlen);
    }
}

void hexl(FILE *fp) 
{
    register long address;
    char string[LENGTH+2];
    address = 0;
    string[0] = ' ';
    string[LENGTH+1] = '\0';
    for (;;)
    {
        register int i, c = 0;

        for (i=0; i < LENGTH; ++i)
        {
            if ((c = getc (fp)) == EOF)
            {
                if (!i)
                    break;
                fputs ("  ", stdout);
                string[i+1] = '\0';
            }
            else
            {
                if (!i)
                    printf ("%08lx: ", address);

                if (iso_flag)
                    string[i+1] =
                        (c < 0x20 || (c >= 0x7F && c < 0xa0)) ? '.' :c;
                else
                    string[i+1] = (c < 0x20 || c >= 0x7F) ? '.' : c;

                printf ("%02x", c);
            }

            if ((i&group_by) == group_by)
                putchar (' ');
        }

        if (i)
            puts (string);

        if (c == EOF)
            break;

        address += LENGTH;
    }
}

void
usage ()
{
  fprintf (stderr, "usage: %s [-de] [-iso]\n", progname);
  exit (EXIT_FAILURE);
}

void hexl_line(char *s) 
{
    int i = 0;
    assert(s);
    while (*s) {
        printf("%02x", (unsigned char)*s);
        if ( (i&group_by) == group_by ) {
            putchar (' ');
        }
        s++; i++;
    }
    if ( i != LENGTH ) {
        for ( i; i<LENGTH; i++ ) {
            printf("  ");
            if ( (i&group_by) == group_by ) {
                putchar (' ');
            }
        }
    }
}

void noctrl(char *s)
{
    assert(s);
    unsigned char c;
    if ( tocode == NULL ) {
        while (*s) {
            c = (unsigned char)*s;
            if (iso_flag)
                *s = (c < 0x20 || (c >= 0x7F && c < 0xa0)) ? '.' :c;
            else
                *s = (c < 0x20 || c >= 0x7F) ? '.' : c;
            s++;
        }
    }
    else {
        while (*s) {
            if ( (unsigned char)*s < 0x20 ) {
                *s = '.';
            }
            s++;
        }
    }
}
